home *** CD-ROM | disk | FTP | other *** search
- /**********
- * Sound Confusion
- *
- * Copyright © 1992 Bernie Bernstein
- * 9/11/92 - 10/14/92
- ***********/
-
-
- #include <GestaltEqu.h>
- #include <Memory.h>
- #include <OSEvents.h>
- #include <Sound.h>
- #include <SoundInput.h>
- #include <StdIO.h>
- #include <Types.h>
-
- #include "Parrot.h"
-
- /* how much memory should the program reserve after making the buffers? */
- #define kExtraMem 0x5000
-
- /* reset the channels every kResetInterval seconds */
- #define kResetInterval 30
-
-
- /*The HeaderSize constant is used to skip 20 bytes of the buffer when calling bufferCmd.
- The first 20 byte of the sound header need to be skipped so that the bufferCmd will be
- pointing at a SoundHeader Record and not an 'snd ' resource header. */
- #define kHeaderSize 20
-
- typedef struct {
- short OnOff;
- short Level;
- short Length;
- } Level;
-
-
- Boolean inStereo;
- short gThreshold;
- long gBuffSize;
- SCGlobals *globs;
- BufInfo gExtraBuffer;
- Boolean gPlayNRecord;
-
-
- /***
- * BufPlay
- * This routine takes an snd buffer and a sound channel and turns the information
- * into a bufferCmd to the channel.
- ***/
- void BufPlay (Handle Buf, SndChannelPtr channel)
- {
- SndCommand localSndCmd;
- OSErr err;
-
- localSndCmd.cmd = bufferCmd;
- localSndCmd.param1 = 0;
- localSndCmd.param2 = (long) ((*Buf) + kHeaderSize);
-
- err = SndDoCommand (channel, &localSndCmd, false);
- if (err != noErr)
- AlertUser(err, iPlaying);
- return;
- }
-
-
- /***
- * SetUpSounds
- * SetUpSounds is a routine which takes a buffer handle, sound headers size and
- * sample rate value and turns it into a snd buffer with the proper header.
- * It then returns the buffer handle back to the caller.
- ***/
- Handle SetUpSounds (Handle Buf, short *HeaderSize, Fixed sampRate)
- {
- OSErr err;
- short dummy;
-
- Buf = NewHandle(gBuffSize);
- if ((err = MemError()) != noErr || Buf == nil)
- AlertUser(err, iSetupBuffer);
- MoveHHi (Buf);
- if ((err = MemError()) != noErr)
- AlertUser(err, iSetupBuffer);
- HLock (Buf);
- if ((err = MemError()) != noErr)
- AlertUser(err, iSetupBuffer);
-
- /* The call to SetupSndHeader is done twice in order to make sure that the
- Header size is set correctly. */
-
- err = SetupSndHeader (Buf, 1, sampRate, 8, 'NONE', 0x3C, 0, HeaderSize);
- if (err != noErr)
- AlertUser(err, iSetupBuffer);
- err = SetupSndHeader (Buf, 1, sampRate, 8, 'NONE', 0x3C, gBuffSize - *HeaderSize, &dummy);
- if (err != noErr)
- AlertUser(err, iSetupBuffer);
-
- return (Buf);
- }
-
- /***
- * RndRange
- * Pick a random number from min to max.
- ***/
- short RndRange(short min, short max)
- {
- unsigned short r = (unsigned short)Random();
- short d = max-min+1;
- return r / (0x10000/d) + min;
- }
-
-
- /***
- * PickPlayableBuffer
- * Return the index of a buffer which is ready to be played. It randomly
- * picks one from the playable buffers, that is, ones which have some
- * sound in them. If it cant find one, then it returns a value too big.
- ***/
- short PickPlayableBuffer(BufInfo *buffers, short max)
- {
- short rnd;
- short i;
-
- for(rnd = RndRange(0,max-1), i=0;
- !buffers[rnd].playable;
- i++)
- {
- /* try 100 times to find a good buffer before quitting */
- /* oh shuddup, I know its dumb. */
- if (i>100)
- return max+1;
-
- rnd = RndRange(0,max-1);
- }
- return rnd;
- }
-
-
- /***
- * PickReadableBuffer
- * Return the index of a buffer which can read some new sounds
- * First it fills up each buffer, then it randomly picks new ones
- * once they are all full.
- ***/
- short PickReadableBuffer(BufInfo *buffers, short max)
- {
- short rnd;
- short i;
-
- for(i=0; i<max; i++)
- {
- if (!buffers[i].playable)
- return i;
- }
-
- return RndRange(0,max-1);
- }
-
-
- #define Abs(x) ((x<0)?-(x):x)
- #define BlankSpace 0x1000
-
- /***
- * TrimBuffer
- * If the buffer is silent, then return false. Otherwise, reduce the
- * buffer to just the parts that have sound.
- ***/
- long TrimBuffer(Handle buffer, long headerlen)
- {
- Boolean result = false;
- long oldBuffSize = GetHandleSize(buffer);
- long oldEnd = 0;
- long oldStart = headerlen;
- long newIndex = 0;
- long copySize = 0;
- long bufByte = 0;
- Boolean firstTime = true;
- Handle tmpbuffer = gExtraBuffer.buffer;
-
- /* copy header into new buffer */
- BlockMove(*buffer, *tmpbuffer, headerlen);
-
- /* determine if the buffer was loud enough */
- /* loud means that a byte in the buffer is far enough away from the */
- /* center to get above the gThreshold */
- for (bufByte=0; bufByte<oldBuffSize-headerlen; bufByte++)
- {
- short delta = 0x80 - (unsigned char)(*buffer)[headerlen + bufByte];
- if (Abs(delta) << 1 > gThreshold)
- {
- /* the first time we hear something, save the previous BlankSpace bytes */
- /* until now. */
- if (firstTime)
- {
- /* If we are close to the beginning, copy from the beginning. */
- /* otherwise, copy blank space until this spot */
- if (bufByte > BlankSpace)
- oldStart = bufByte - BlankSpace;
- else
- oldStart = 0;
-
- /* If we are near the end, copy to the end. */
- /* otherwise, copy blank space after this spot */
- oldEnd = bufByte + BlankSpace;
- if (headerlen + oldEnd > oldBuffSize)
- oldEnd = oldBuffSize - headerlen;
- copySize = oldEnd - oldStart;
-
- BlockMove(&(*buffer)[headerlen+oldStart], &(*tmpbuffer)[headerlen+newIndex], copySize);
- newIndex += copySize;
- bufByte = oldEnd;
- firstTime = false;
- }
- else
- {
- /* After the first time, bufByte > BlankSpace. */
- oldStart = bufByte - BlankSpace;
- if (oldStart < oldEnd)
- oldStart = oldEnd+1;
-
- oldEnd = bufByte + BlankSpace;
- if (headerlen + oldEnd > oldBuffSize)
- oldEnd = oldBuffSize - headerlen;
- copySize = oldEnd - oldStart;
-
- BlockMove(&(*buffer)[headerlen+oldStart], &(*tmpbuffer)[headerlen+newIndex], copySize);
- newIndex += copySize;
- bufByte = oldEnd;
- }
- result = true;
- }
- }
-
- if (result)
- {
- BlockMove(*tmpbuffer, *buffer, newIndex+headerlen);
- return newIndex;
- }
- else
- {
- return 0L;
- }
- }
-
-
-
- #define TestBit(l,b) ((l>>b)&0x01)
-
- /***
- * InitSoundConfusion
- * Allocate memory and initialize the sound driver
- ***/
- void InitSoundConfusion(void)
- {
- Level myLevel;
- OSErr err;
- long feature;
- BufInfo *Buffers = nil;
- short i;
- short numBuffers;
- short meterState;
- SPBPtr RecordRec = nil;
- Str255 settingStr;
- long tmplong;
-
- /* use Gestalt to make sure the app will work */
- err = Gestalt(gestaltSoundAttr, &feature);
- if (err == noErr)
- {
- if (!TestBit(feature, gestaltHasSoundInputDevice))
- {
- AlertUser(0, iNoInput);
- ExitToShell();
- }
- else
- {
-
- struct {
- short ignore;
- char majSys;
- char minSys;
- } sysVersion;
-
- /* does the machine have stereo? */
- inStereo = TestBit(feature, gestaltStereoCapability);
-
- /* can the machine play and record simultaneously? */
- /* prior to system 7.1, we could assume that if the machine */
- /* had stereo, then it has the better Sound Chip and thus */
- /* can play and record at the same time. 7.1 added the */
- /* gestaltPlayAndRecord bit. I dont have 7.1 yet, but I */
- /* will take Jim Reekes word for it. I put it in my */
- /* GestaltEqu.h file as gestaltPlayAndRecord = 6 */
- err = Gestalt(gestaltSystemVersion, (long*)&sysVersion);
- if (err != noErr)
- {
- AlertUser(err, iGestaltFailed);
- ExitToShell();
- }
- if (((sysVersion.majSys == 7) && (sysVersion.minSys >= 0x10)) || (sysVersion.majSys > 7))
- gPlayNRecord = TestBit(feature, gestaltPlayAndRecord);
- else
- gPlayNRecord = inStereo;
-
- }
- }
- else
- {
- AlertUser(err, iGestaltFailed);
- ExitToShell();
- }
-
- /* the threshold is a setting in the STR# resource */
- GetIndString(settingStr, rSettingStrings, iThreshold);
- StringToNum(settingStr, &tmplong);
- gThreshold = tmplong;
-
- /* the buffer size is also keps as a string */
- GetIndString(settingStr, rSettingStrings, iBufferSize);
- StringToNum(settingStr, &tmplong);
- gBuffSize = tmplong;
-
- /* There was a time when I was thinking of making an INIT, so I was */
- /* going to keep all globals in a handle, but I changed my mind */
- /* some of these can just be normal global variables, but what */
- /* the heck. */
- globs = (SCGlobals*)NewPtr(sizeof(SCGlobals));
- if ((err = MemError()) != noErr || globs == nil)
- {
- AlertUser(err, iMakingGlobals);
- return;
- }
- globs->fullBuffer = true;
- globs->loudEnough = false;
- globs->bufferGettingFilled = 0;
- globs->leftChan = nil;
- globs->rightChan = nil;
- globs->buffers = nil;
- globs->numBuffers = 0;
- globs->RecordRec = nil;
- globs->direction = iPlay; /* next time through, it records */
-
- /* Open sound input drive (whichever one is selected in the sound cdev) */
- err = SPBOpenDevice (nil, siWritePermission, &globs->SoundRefNum);
- if (err != noErr)
- {
- AlertUser(err, iOpeningDevice);
- return;
- }
-
- /* Get the sample rate information for the snd header */
- err = SPBGetDeviceInfo (globs->SoundRefNum,siSampleRate, (Ptr) &globs->sampleRate);
- if (err != noErr)
- {
- AlertUser(err, iGettingRate);
- return;
- }
-
- /* build the RecordRec pointer and fill in the fields */
- RecordRec = (SPBPtr) NewPtr(sizeof (SPB));
- if ((err = MemError()) != noErr || RecordRec == nil)
- {
- AlertUser(err, iMakingRecordRec);
- return;
- }
- globs->RecordRec = RecordRec;
-
- /* how many buffers can we make? (leave space for an extra buffer and then some memory) */
- numBuffers = ((FreeMem() - kExtraMem) / gBuffSize) - 1;
- if (numBuffers < 1)
- {
- AlertUser(0, iMemory);
- return;
- }
-
- Buffers = (BufInfo*)NewPtr(sizeof(BufInfo)*numBuffers);
- if ((err = MemError()) != noErr || Buffers == nil)
- {
- AlertUser(err, iAllocatingBuffers);
- return;
- }
- globs->buffers = Buffers;
- globs->numBuffers = numBuffers;
-
-
- /* build the snd buffers */
- for (i=0; i<numBuffers; i++)
- {
- Buffers[i].buffer = SetUpSounds(Buffers[i].buffer, &Buffers[i].headerlength, globs->sampleRate);
- Buffers[i].buffSize = gBuffSize - Buffers[i].headerlength;
- Buffers[i].playable = false;
- }
-
- gExtraBuffer.buffer = SetUpSounds(gExtraBuffer.buffer, &gExtraBuffer.headerlength, globs->sampleRate);
-
- RecordRec->inRefNum = globs->SoundRefNum;
- RecordRec->count = gBuffSize - Buffers[0].headerlength;
- RecordRec->milliseconds = 0;
- RecordRec->bufferLength = gBuffSize - Buffers[0].headerlength;
- RecordRec->bufferPtr = (Ptr) ((*Buffers[0].buffer) + Buffers[0].headerlength);
- RecordRec->completionRoutine = (ProcPtr) &MyRecComp;
- RecordRec->interruptRoutine = nil;
- RecordRec->userLong = (long)globs;
- RecordRec->error = 0;
- RecordRec->unused1 = 0;
-
-
- #ifdef IMOVEDTHISINTORESETCHANNELS
-
- /* if the machine has stereo, then open the right and left channels */
- if (inStereo)
- {
- /* open the left channel which I can play from */
- globs->leftChan = nil;
- err = SndNewChannel (&globs->leftChan, sampledSynth, initChanLeft, nil);
- if (err != noErr)
- {
- AlertUser(err, iMakingLeft);
- return;
- }
-
- /* open the right channel which I can play from */
- globs->rightChan = nil;
- err = SndNewChannel (&globs->rightChan, sampledSynth, initChanRight, nil);
- if (err != noErr)
- {
- AlertUser(err, iMakingRight);
- return;
- }
- }
- else
- {
- /* open a mono channel and keep in the 'left' variable */
- globs->leftChan = nil;
- err = SndNewChannel (&globs->leftChan, sampledSynth, initMono, nil);
- if (err != noErr)
- {
- AlertUser(err, iMakingMono);
- return;
- }
- }
- #endif
-
- globs->firstTime = true;
-
- // ResetChannels();
-
- }
-
-
- /***
- * ResetChannels
- *
- * Close (if not already) and then open the sound channel(s)
- ***/
- void ResetChannels()
- {
- SndCommand mycmd;
- OSErr err;
- static Boolean channelsExist = false;
-
- if (channelsExist)
- {
- /* stop playing sounds now */
- mycmd.cmd = quietCmd;
- mycmd.param1 = 0;
- mycmd.param2 = 0;
- if (globs->leftChan)
- {
- /* tell the left channel to shut up */
- err = SndDoImmediate (globs->leftChan, &mycmd);
- if (err != noErr)
- {
- AlertUser(err, iClosing);
- return;
- }
-
- /* kill the left channel */
- err = SndDisposeChannel (globs->leftChan,false);
- if (err != noErr)
- {
- AlertUser(err, iClosing);
- return;
- }
- }
- if (inStereo && globs->rightChan)
- {
- /* tell the right channel to shut up */
- err = SndDoImmediate (globs->rightChan, &mycmd);
- if (err != noErr)
- {
- AlertUser(err, iClosing);
- return;
- }
-
- /* kill the right channel */
- err = SndDisposeChannel (globs->rightChan,false);
- if (err != noErr)
- {
- AlertUser(err, iClosing);
- return;
- }
- }
- }
-
- /* if the machine has stereo, then open the right and left channels */
- if (inStereo)
- {
- /* open the left channel which I can play from */
- globs->leftChan = nil;
- err = SndNewChannel (&globs->leftChan, sampledSynth, initChanLeft, nil);
- if (err != noErr)
- {
- AlertUser(err, iMakingLeft);
- return;
- }
-
- /* open the right channel which I can play from */
- globs->rightChan = nil;
- err = SndNewChannel (&globs->rightChan, sampledSynth, initChanRight, nil);
- if (err != noErr)
- {
- AlertUser(err, iMakingRight);
- return;
- }
- }
- else
- {
- /* open a mono channel and keep in the 'left' variable */
- globs->leftChan = nil;
- err = SndNewChannel (&globs->leftChan, sampledSynth, initMono, nil);
- if (err != noErr)
- {
- AlertUser(err, iMakingMono);
- return;
- }
- }
-
- channelsExist = true;
- }
-
-
- /***
- * ConfuseSound
- * This is called during idle time from the event loop.
- * It starts recording from a buffer and playing from another one.
- ***/
- void ConfuseSound(void)
- {
- short dummy;
- OSErr err;
- BufInfo *Buffers;
- short i;
- SndCommand mycmd;
- long bufByte;
- short bufNum;
- Boolean stillWorking = false;
- Boolean stillPlaying = false;
- Boolean stillRecording = false;
- SCStatus chanStatL;
- SCStatus chanStatR;
- static unsigned long lastResetTime = 0L;
- unsigned long currTime;
-
- GetDateTime(&currTime);
-
- if (currTime - lastResetTime > kResetInterval)
- {
- ResetChannels();
- lastResetTime = currTime;
- }
-
- /* easier access to the buffers */
- Buffers = globs->buffers;
-
- /* some stuff to prevent us from playing and recording simultaneously */
- /* on a machine that is incapable of it. */
- if (globs->firstTime)
- {
- stillPlaying = false;
- stillRecording = false;
- }
- else
- {
- if (globs->direction == iPlay || gPlayNRecord)
- {
- err = SndChannelStatus(globs->leftChan, sizeof(SCStatus), &chanStatL);
- if (err != noErr)
- AlertUser(err, iPlaying);
- stillPlaying = chanStatL.scChannelBusy;
-
- if (inStereo && !stillPlaying)
- {
- err = SndChannelStatus(globs->rightChan, sizeof(SCStatus), &chanStatR);
- if (err != noErr)
- AlertUser(err, iPlaying);
- stillPlaying = chanStatR.scChannelBusy;
- }
-
- }
- if (globs->direction == iRecord || gPlayNRecord)
- {
- stillRecording = !globs->fullBuffer;
- }
- }
-
- stillWorking = stillPlaying || stillRecording;
-
- /* if we cant play and record, then exit if we are in the middle of */
- /* playing or recording. we can pass through as soon as the process is done */
- if ((!gPlayNRecord && stillWorking) || (stillPlaying && stillRecording))
- return;
-
-
- /* if this is the first time through, then prepare to record into a buffer */
- /* if a buffer recording has finished (ie, the buffer is full), then prepare */
- /* to record into another buffer */
- if (!stillRecording)
- {
- globs->fullBuffer = false;
-
- if (!globs->firstTime && (gPlayNRecord || globs->direction == iRecord))
- {
- long newSize;
- bufNum = globs->bufferGettingFilled;
-
- /* trim the buffer by removing silent (quiet) sections */
- newSize = TrimBuffer(Buffers[bufNum].buffer, Buffers[bufNum].headerlength);
- Buffers[bufNum].playable = globs->loudEnough = (newSize > 0);
- Buffers[bufNum].buffSize = newSize;
-
- /* if the buffer was loud enough, then prepare to record the next readable buffer */
- if (globs->loudEnough)
- {
- short nextbuf = PickReadableBuffer(Buffers, globs->numBuffers);
- globs->bufferGettingFilled = nextbuf;
- globs->loudEnough = false;
- Buffers[nextbuf].playable = false;
- HUnlock(Buffers[nextbuf].buffer);
- SetHandleSize(Buffers[nextbuf].buffer, gBuffSize);
- MoveHHi(Buffers[nextbuf].buffer);
- HLock(Buffers[nextbuf].buffer);
- globs->RecordRec->bufferPtr = (*Buffers[nextbuf].buffer) + Buffers[nextbuf].headerlength;
- }
- }
-
- globs->firstTime = false;
-
- if (gPlayNRecord)
- {
- /* start recording into the buffer asynchronously */
- err = SPBRecord (globs->RecordRec, true); // start recording
- if (err != noErr)
- AlertUser(err, iRecording);
- }
- }
-
- if (gPlayNRecord && !stillPlaying)
- {
- /* pick a buffer to play from */
- i = PickPlayableBuffer(Buffers, globs->numBuffers);
- if (i<=globs->numBuffers)
- {
- long bsize = Buffers[i].buffSize;
- err = SetupSndHeader (Buffers[i].buffer, 1, globs->sampleRate, 8, 'NONE', 0x3C, bsize, &dummy);
- if (err != noErr)
- AlertUser(err, iPlaying);
- /* play it asynchronously */
- BufPlay(Buffers[i].buffer,
- inStereo ? (RndRange(0,1) ? globs->leftChan : globs->rightChan) : globs->leftChan);
- }
- }
-
-
- /* if we cant play and record simultaneously, then do t`he one that */
- /* we werent doing last. in other words, take turns playing and recording */
- if (!gPlayNRecord)
- {
- if (globs->direction == iPlay)
- {
- globs->fullBuffer = false;
- /* start recording into the buffer asynchronously */
- err = SPBRecord (globs->RecordRec, true); // start recording
- if (err != noErr)
- AlertUser(err, iRecording);
- globs->direction = iRecord;
- }
- else /* if (globs->direction == iRecord) */
- {
- /* pick a buffer to play from */
- i = PickPlayableBuffer(Buffers, globs->numBuffers);
- if (i<=globs->numBuffers)
- {
- long bsize = Buffers[i].buffSize;
- err = SetupSndHeader (Buffers[i].buffer, 1, globs->sampleRate, 8, 'NONE', 0x3C, bsize, &dummy);
- if (err != noErr)
- AlertUser(err, iPlaying);
- /* play it asynchronously */
- BufPlay(Buffers[i].buffer,
- inStereo ? (RndRange(0,1) ? globs->leftChan : globs->rightChan) : globs->leftChan);
- }
- globs->direction = iPlay;
- globs->fullBuffer = true;
- }
- }
- }
-
-
- /***
- * StopConfusion
- * Deallocate memory and close stuff.
- ***/
- void StopConfusion(void)
- {
- OSErr err;
- SndCommand mycmd;
- short i;
-
- if (!globs)
- return;
-
- /* stop playing sounds now */
- mycmd.cmd = quietCmd;
- mycmd.param1 = 0;
- mycmd.param2 = 0;
- if (globs->leftChan)
- {
- /* tell the left channel to shut up */
- err = SndDoImmediate (globs->leftChan, &mycmd);
- if (err != noErr)
- AlertUser(err, iClosing);
-
- /* kill the left channel */
- err = SndDisposeChannel (globs->leftChan,false);
- if (err != noErr)
- AlertUser(err, iClosing);
- }
- if (inStereo && globs->rightChan)
- {
- /* tell the right channel to shut up */
- err = SndDoImmediate (globs->rightChan, &mycmd);
- if (err != noErr)
- AlertUser(err, iClosing);
-
- /* kill the right channel */
- err = SndDisposeChannel (globs->rightChan,false);
- if (err != noErr)
- AlertUser(err, iClosing);
- }
-
- if (globs->SoundRefNum != 0)
- {
- /* close the input device */
- SPBCloseDevice (globs->SoundRefNum);
- }
-
- if (globs->buffers)
- {
- /* kill the snd buffers */
- for (i=0; i<globs->numBuffers; i++)
- {
- if (globs->buffers[i].buffer)
- {
- HUnlock(globs->buffers[i].buffer);
- DisposeHandle(globs->buffers[i].buffer);
- }
- }
- }
-
- if (gExtraBuffer.buffer)
- {
- HUnlock(gExtraBuffer.buffer);
- DisposeHandle(gExtraBuffer.buffer);
- }
-
- /* kill the buffers */
- DisposePtr ((Ptr) globs->buffers);
- DisposePtr ((Ptr) globs->RecordRec);
- DisposePtr ((Ptr) globs);
- }
-
-
- /**********************************/
-
- void DoAbout()
- {
- Str31 numBufsStr;
- Str31 numFilledStr;
- short numFilled = 0;
- short i;
- short itemHit;
- DialogPtr dlg;
-
- for (i=0; i<globs->numBuffers; i++)
- {
- if (globs->buffers[i].playable)
- numFilled++;
- }
- NumToString((long)globs->numBuffers, numBufsStr);
- NumToString((long)numFilled, numFilledStr);
- ParamText(numBufsStr, numFilledStr, "", "");
-
- dlg = GetNewDialog(rAboutAlert, NULL, (WindowPtr)-1L);
- ModalDialog(NULL, &itemHit);
- DisposDialog(dlg);
- }
-
-
- /***
- * MyRecComp
- *
- * This is the Completion Routine which is called every time the buffer,
- * which is being recorded into, is full.
- ***/
- pascal void MyRecComp (SPBPtr inParamPtr)
- {
- SCGlobals *glob;
-
- glob = (SCGlobals*)inParamPtr->userLong;
- glob->fullBuffer = true;
-
- if (inParamPtr->error != noErr)
- {
- AlertUser(inParamPtr->error, iPlaying);
- }
-
- return;
- }
-